#ifndef AST_H
#define AST_H

#include <string>
#include <vector>

enum ValType {
    T_TEXT,
    T_INTEGER,
};

enum IDType {
    ID_VAR,
    ID_FUNC,
};

class BaseAST
{
public:
//    BaseAST() {}
    virtual ~BaseAST() {}
    //virtual void visitor() = 0;
//    virtual Value *Codegen() = 0;
};

class DeclAST: virtual public BaseAST
{};

class ExprAST: virtual public BaseAST
{
//    int value;
//public:
//    ExprAST(int v = 0): value(v) {}
};

class StmtAST: virtual public BaseAST
{
};

// LITERAL
class LiteralAST: virtual public BaseAST
{
    ValType type;
    int val;
    std::string text;
public:
    LiteralAST(ValType t = T_INTEGER, int v = 0, std::string txt = ""): type(t), val(v), text(txt) {}
};

class IdentAST: virtual public BaseAST
{
//    IDType type;
    std::string id;
//    union {
//        VariableAST *var;
//        FuncAST *func;
//    };
public:
//    IdentAST(IDType t = ID_VAR, std::string n): type(t), name(n) {}
    IdentAST(std::string i): id(i) {}
    const std::string getId() const { return this->id; }
    void visitor();
    //~IdentAST() {}
};

// INTEGER | TEXT
class LiteralExprAST: public LiteralAST, public ExprAST
{
public:
    LiteralExprAST(ValType t = T_INTEGER, int v = 0, std::string txt = ""): LiteralAST(t, v, txt) {}
};

// VARIABLE
class VariableExprAST: public IdentAST, public ExprAST
{
public:
    VariableExprAST(std::string i): IdentAST(i) {}
};


// - `operand`
class UnaryExprAST: public ExprAST
{
    char opcode;
    ExprAST *operand;
public:
    UnaryExprAST(char op, ExprAST *e): opcode(op), operand(e) {}
};

// LHS + RHS
// LHS - RHS
// LHS * RHS
// LHS / RHS
class BinaryExprAST: public ExprAST
{
    char opcode;
    ExprAST *LHS, *RHS;
public:
    BinaryExprAST(char op, ExprAST *lhs, ExprAST *rhs): opcode(op), LHS(lhs), RHS(rhs) {}
};


// '{'
//      `decls`
//      `stmts`
// '}'
class BlockStmtAST: public StmtAST
{
    std::vector<DeclAST *> decls;
    std::vector<StmtAST *> stmts;
public:
    BlockStmtAST(std::vector<DeclAST *> d, std::vector<StmtAST *> s): decls(d), stmts(s) {}
    void visitor();
};

// FUNC `id` '(' args ')'
class FuncDeclAST: public DeclAST
{
    IdentAST *id;
    std::vector<IdentAST *> args;
public:
    FuncDeclAST(IdentAST *i, std::vector<IdentAST *> s): id(i), args(s) {}
    const std::string &getId() const { return this->id->getId(); }
    void visitor();
};

// FUNC name '(' args ')'  // `Proto`
// '{' `Body` '}'
class FuncAST: public BaseAST
{
    FuncDeclAST *Proto;
    BlockStmtAST *Body;
public:
    FuncAST(FuncDeclAST *d, BlockStmtAST *b): Proto(d), Body(b) {}
    void visitor();
};

class FuncListAST: public BaseAST
{
    std::vector<FuncAST *> list;
public:
    FuncListAST(std::vector<FuncAST *> l): list(l) {}
    FuncAST *add(FuncAST *func);
    void visitor();
};

// callee(args...)
class FuncExprAST: public ExprAST
{
    std::string callee;
    std::vector<ExprAST *> args;
public:
    FuncExprAST(std::string c, std::vector<ExprAST *> a): callee(c), args(a) {}
};

// VAR v1, v2, v3, ...
class DeclStmtAST: public DeclAST, public StmtAST
{
    std::vector<IdentAST *> ids;
public:
    DeclStmtAST(std::vector<IdentAST *> i): ids(i) {}
};

// '(' `expr` ')'
class ExprStmtAST: public ExprAST, public StmtAST
{
    ExprAST *expr;
public:
    ExprStmtAST(ExprAST *);
};

// `var` := `expr`
class AssignStmtAST: public StmtAST
{
    IdentAST *id;
    ExprAST *expr;
public:
    AssignStmtAST(IdentAST *i, ExprAST *e): id(i), expr(e) {}
};

// RETURN `expr`
class ReturnStmtAST: public StmtAST
{
    ExprAST *expr;
public:
    ReturnStmtAST(ExprAST *e): expr(e) {}
};

// PRINT `item[0]`, `item[1]`, ...
class PrintStmtAST: public StmtAST
{
    std::vector<ExprAST *> items;
public:
    PrintStmtAST(std::vector<ExprAST *> items): items(items) {}
};

// CONTINUE
class NULLStmtAST: public StmtAST
{
    // CONTINUE
};

// IF `Cond` THEN `Then` FI
// IF `Cond` THEN `Then` ELSE `Else` FI
class IfStmtAST: public StmtAST
{
    ExprAST *Cond;
    StmtAST *Then, *Else;
public:
   IfStmtAST(ExprAST *c, StmtAST *t, StmtAST *e): Cond(c), Then(t), Else(e) {}
};

// WHILE `Cond` DO `Body` DONE
class WhileStmtAST: public StmtAST
{
    ExprAST *Cond;
    StmtAST *Body;
public:
    WhileStmtAST(ExprAST *c, StmtAST *b): Cond(c), Body(b) {}
};


#endif // AST_H
